import {
	defaultAvatar as DEFAULT_AVATAR
} from '@/config/staticConfig.js'

const APP_VERSION = '1.0.0'
const APP_PACKAGE = 'com.farsunset.cim'

/*
 *特殊的消息类型，代表被服务端强制下线
 */
const ACTION_999 = '999'
const DATA_HEADER_LENGTH = 1

const MESSAGE = 2
const REPLY_BODY = 4
const SENT_BODY = 3
const PING = 1
const PONG = 0

/*
 * 握手鉴权常量
 */
const KEY_HANDSHAKE = 'client_handshake'
const KEY_CLIENT_BIND = 'client_bind'
const KEY_CLIENT_ROOM = 'client_room'
const KEY_SEND_MESSAGE = 'client_message'
const KEY_REVOKE_MESSAGE = 'client_revoke'
const KEY_CLIENT_CLOSE = 'client_closed'

const CODE_UNAUTHORIZED = '401'
const CODE_OK = '200'
const CODE_ERROR = '500'

const MESSAGE_SEND_OK = 0
const MESSAGE_SEND_SERVICE_OK = 1
const MESSAGE_SEND_ERROR = 2

const MESSAGE_RECEIVE_OK = 3
const MESSAGE_RECEIVE_ERROR = 4

const MESSAGE_REVOKE_OK = 5
const MESSAGE_REVOKE_ERROR = 6
const MESSAGE_REVOKE_SERVICE_OK = 7
const MESSAGE_REVOKE_SERVICE_ERROR = 8

const ROOM_SEND_SERVICE_OK = 9
const ROOM_RECEIVE_ERROR = 10

/**
 * PONG字符串转换后
 * @type {Uint8Array}
 */
const PONG_BODY = 'PONG'

class WebSocket {
	constructor(url, time) {
		this.url = url
		this.socketInstance = null // websocket实例
		this.isStopManually = false // WebSocket 是否手动断开连接
		this.isConnect = false // WebSocket 连接是否成功
		this.isOpen = false // WebSocket 连接是否已打开
		this.isBind = false // WebSocket 连接是否已绑定
		this.timeoutNumber = time || 60 // 心跳检测间隔
		this.heartbeatTimer = null // 心跳检测定时器
		this.reconnectTimer = null // 断线重连定时器
		this.reconnectInterval = 10 // 重连等待时间(单位秒)
		this.reconnectNum = 0 // 重连次数
		this.reconnectMaxNum = 9 //  最大重连次数
		this.bindNum = 0 // 绑定账号次数
		this.bindMaxNum = 9 // 绑定账号次数
		this.roomMap = {}
		this.roomId = null // 绑定房间号
		this.unreadCount = {} // 根据类型获得未读消息数量 
		this.account = ''
	}

	// websocket 初始化连接
	initSocketListeners() {
		if (!this.isConnect) {
			consoleError('WebSocket 未连接成功，不进行初始化操作！')
			return
		}

		// consoleLog('WebSocket 开始初始化...')
		// 监听 WebSocket 连接打开事件
		try {
			this.socketInstance.onOpen(() => {
				// consoleLog('WebSocket 已打开')
				clearInterval(this.heartbeatTimer)
				clearTimeout(this.reconnectTimer)
				this.isOpen = true

				if (this.account) {
					this.bind(this.account)
				}
			})

			// 监听 WebSocket 接受到服务器的消息事件
			this.socketInstance.onMessage((e) => {
				let data = JSON.parse(e.data)
				let type = data.type
				let body = data.content

				if (type === PING) {
					this.pong()
					return
				}

				if (type === MESSAGE) {
					let message = JSON.parse(body)
					// consoleLog(` WebSocket 接收到服务器推送的聊天消息`, message)

					// 被强制下线之后，不再继续连接服务端
					this.isStopManually = message.action === ACTION_999

					this.formatMessageByRoom(message)
					return
				}

				if (type === REPLY_BODY) {
					let reply = JSON.parse(body)
					// consoleLog(`WebSocket 接收到服务器推送的业务消息`, reply)
					/*
					 * 判断是否是握手鉴权失败
					 * 终止后续自动重连
					 */

					if (reply.key === KEY_CLIENT_BIND && reply.code === CODE_OK) {
						// consoleLog('WebSocket 鉴权成功')

						// 聊天室类型（0-群聊 1-私聊 3-组局 4-匿名 5-快速建群）
						this.unreadCount = {
							message_1: Number(reply.data.type1Num || '0'),
							message_0: Number(reply.data.type0Num || '0'),
							message_4: Number(reply.data.type4Num || '0'),
							party: Number(reply.data.type3Num || '0'),
							quickGroup: Number(reply.data.type5Num || '0'),
						}
						this.onUnreadCountChange(this.unreadCount)
						this.setTabBarBadge()
					}

					// 链接鉴权失败
					if (reply.key === KEY_HANDSHAKE && reply.code === CODE_UNAUTHORIZED) {
						consoleError('WebSocket 鉴权失败')
						this.isStopManually = true
					}

					// 绑定/解绑房间成功，返回消息唯一标识
					if (reply.key === KEY_CLIENT_ROOM && reply.code === CODE_OK) {
						// consoleLog('WebSocket 绑定/解绑房间成功后服务器的反馈，返回唯一标识', reply.data)
						this.onChatMessageChange({
							code: ROOM_SEND_SERVICE_OK,
							data: reply.data,
						})
					}

					// 绑定/解绑房间失败，返回失败原因
					if (reply.key === KEY_CLIENT_ROOM && reply.code === CODE_ERROR) {
						consoleError('WebSocket 绑定/解绑房间失败后服务器的反馈，返回失败原因', reply.data)
						this.onChatMessageChange({
							code: ROOM_RECEIVE_ERROR,
							data: reply.data,
						})
					}

					// 发送消息成功，返回消息唯一标识
					if (reply.key === KEY_SEND_MESSAGE && reply.code === CODE_OK) {
						// consoleLog('WebSocket 发送消息成功后服务器的反馈，返回消息唯一标识', reply.data)
						this.onChatMessageChange({
							code: MESSAGE_SEND_SERVICE_OK,
							data: reply.data,
						})
					}

					// 发送消息失败，返回失败原因
					if (reply.key === KEY_SEND_MESSAGE && reply.code === CODE_ERROR) {
						consoleError('WebSocket 发送消息失败后服务器的反馈，返回失败原因', reply.data)
						this.onChatMessageChange({
							code: MESSAGE_RECEIVE_ERROR,
							data: reply.data,
						})
					}

					// 撤回消息请求响应-成功，返回消息唯一标识
					if (reply.key === KEY_REVOKE_MESSAGE && reply.code === CODE_OK) {
						// consoleLog('WebSocket 撤回消息请求响应-成功，返回消息唯一标识', reply.data)
						this.onChatMessageChange({
							code: MESSAGE_REVOKE_SERVICE_OK,
							data: reply.data,
						})
					}

					// 撤回消息请求响应-失败，返回失败原因
					if (reply.key === KEY_REVOKE_MESSAGE && reply.code === CODE_ERROR) {
						consoleError('WebSocket 撤回消息请求响应-失败，返回失败原因', reply.data)
						this.onChatMessageChange({
							code: MESSAGE_REVOKE_SERVICE_ERROR,
							data: reply.data,
						})
					}
				}
			})

			// 监听 WebSocket 连接关闭事件
			this.socketInstance.onClose(() => {
				// consoleLog('WebSocket 已关闭')
				this.isConnect = false
				this.isOpen = false
				this.isBind = false
				this.socketInstance = null

				if (!this.isStopManually) {
					this.reconnect()
				}
			})

			// 监听 WebSocket 错误事件
			this.socketInstance.onError((e) => {
				consoleError(`WebSocket 报错`, e)
				this.isStopManually = false
			})
		} catch (err) {
			consoleError(`WebSocket 初始化失败`, e)
		}
	}

	formatMessageByRoom(message) {
		let extra = JSON.parse(message.extra || '{}')
		let roomId = extra.roomId || ''
		if (!roomId) {
			return
		}

		if (roomId === this.roomId) {
			// 收到消息后，将消息发送给页面
			this.onChatMessageChange({
				code: MESSAGE_RECEIVE_OK,
				data: message,
			})
		}

		let diff = (message.receiver === message.sender || roomId === this.roomId) ? 0 : 1
		if (diff === 1) {
			this.updateUnreadNum(extra.roomType, 1)
		}

		let tRoomObj = {
			lastMessage: message.content,
			lastMessageType: Number(message.format),
			lastSendTime: message.timestamp,
		}
		if (this.roomMap.hasOwnProperty(roomId)) {
			tRoomObj = {
				...this.roomMap[roomId],
				...tRoomObj,
				name: (extra.roomType === 1 ? extra.singleFriendName : extra.roomName) || this.roomMap[roomId]
					.name,
				chatHead: extra.singleFriendHead || this.roomMap[roomId].chatHead,
				unreadNumber: this.roomMap[roomId].unreadNumber + diff,
			}
		} else {
			tRoomObj = {
				...tRoomObj,
				id: roomId,
				type: extra.roomType,
				name: extra.roomType === 1 ? extra.singleFriendName : extra.roomName,
				chatHead: extra.singleFriendHead,
				friendHead: extra.friendHead,
				unreadNumber: diff,
			}
		}

		this.updateRoomMap([tRoomObj], 'websocket')
	}

	updateRoomMap(roomList, source = 'api', method = 'refresh') {
		if (source === 'api' && method === 'refresh') {
			this.roomMap = {}
		}

		for (let room of roomList) {
			// 因为我不想换行~~~
			let {
				id,
				name,
				type,
				roomIcon,
				unreadNumber,
				lastMessage,
				lastMessageType,
				lastSendTime
			} = room
			let {
				chatHead,
				friendHead,
				createTime
			} = room
			let timestamp = lastSendTime || createTime || 0

			if (!roomIcon) {
				switch (type) {
					case 1:
						roomIcon = chatHead || DEFAULT_AVATAR
						break;
					case 0:
						roomIcon = friendHead ? JSON.parse(friendHead).map(img => img || DEFAULT_AVATAR) :
							(Array(3).fill(DEFAULT_AVATAR))
						break;
					case 4:
						roomIcon = (name || '').substr(0, 1)
						break;
					default:
						roomIcon = DEFAULT_AVATAR
						break;
				}
			}

			this.roomMap[room.id] = {
				id,
				name,
				type,
				roomIcon,
				unreadNumber,
				lastMessage,
				lastMessageType,
				lastSendTime,
				timestamp
			}
		}
		this.onRoomMapChange(this.roomMap)
	}

	// 设置tabbar未读消息数量
	setTabBarBadge() {
		let index3 = this.unreadCount.message_1 + this.unreadCount.message_0 + this.unreadCount.message_4
		if (index3 > 0) {
			uni.setTabBarBadge({
				index: 3,
				text: index3.toString()
			})
		} else {
			uni.removeTabBarBadge({
				index: 3
			});
		}

		// let index4 = this.unreadCount.party + this.unreadCount.quickGroup
		// if (index4 > 0) {
		// 	uni.setTabBarBadge({
		// 		index: 4,
		// 		text: index4.toString()
		// 	})
		// } else {
		// 	uni.removeTabBarBadge({ index: 3 });
		// }
	}
	updateUnreadNum(type, num) {
		switch (type) {
			case 1:
				this.unreadCount.message_1 += num
				break;
			case 0:
				this.unreadCount.message_0 += num
				break;
			case 4:
				this.unreadCount.message_4 += num
				break;
			case 3:
				this.unreadCount.party += num
				break;
			case 5:
				this.unreadCount.quickGroup += num
				break;
		}

		this.onUnreadCountChange(this.unreadCount)
		this.setTabBarBadge()
	}

	deleteRoom(roomInfo, type = 'delete') {
		this.updateUnreadNum(roomInfo.type, roomInfo.unreadNumber * -1)

		if (type === 'delete' && this.roomMap[roomInfo.id]) {
			delete this.roomMap[roomInfo.id]
		}
		if (type === 'reset') {
			this.roomMap[roomInfo.id].unreadNumber = 0
		}
	}

	// websocket 建立连接
	connect() {
		const _this = this
		this.isStopManually = false
		this.socketInstance = uni.connectSocket({
			url: _this.url,
			header: {
				'content-type': 'application/json'
			},
			success: (res) => {
				// consoleLog(`WebSocket 连接成功`, res)
				_this.isConnect = true
			},
			fail: (rej) => {
				consoleError(rej)
				_this.isConnect = false
			}
		})

		this.initSocketListeners()
	}

	// websocket 重新建立连接
	reconnect() {
		clearTimeout(this.reconnectTimer)
		clearInterval(this.heartbeatTimer)

		if (this.reconnectNum > this.reconnectMaxNum) {
			return
		}

		// 如果不是人为关闭的话，进行重连
		if (!this.isStopManually) {
			this.reconnectTimer = setTimeout(() => {
				this.connect()
				this.reconnectNum++
			}, this.reconnectInterval * 1000)
		} else {
			// consoleLog('WebSocket 为手动关闭连接，不进行重连操作！')
		}
	}

	// 关闭 WebSocket 连接
	close(reason = '关闭') {
		const _this = this
		this.socketInstance.close({
			reason,
			code: 1000,
			success() {
				_this.isConnect = false
				_this.isOpen = false
				_this.isBind = false
				_this.isStopManually = true
				_this.socketInstance = null
				clearInterval(_this.heartbeatTimer)
				clearTimeout(_this.reconnectTimer)
				// consoleLog('WebSocket 主动关闭成功')
			},
			fail(e) {
				consoleError(`WebSocket 主动关闭失败`, e)
			}
		})
	}

	async bind(account) {
		// consoleLog(`WebSocket 开始账号绑定 账号：${account}`)

		this.account = account

		if (!this.isConnect) {
			// consoleLog('WebSocket 正在绑定账号操作，但是未连接（bind）')
			// consoleLog('WebSocket 开始连接（bind）')
			this.connect()
			// consoleLog('WebSocket 连接成功（bind）')
		}

		if (!this.isOpen) {
			if (this.bindNum < this.bindMaxNum) {
				this.bindNum++
				// consoleLog(`WebSocket 未开启，正在进行第${this.bindNum}次绑定重试（bind）`)
				setTimeout(() => {
					this.bind(account)
				}, 100)
			} else {
				consoleError(`WebSocket 未开启，无重绑次数，账号绑定失败`, response && response.errMsg)
			}
			return
		}

		if (this.isBind) {
			consoleError(`WebSocket 已绑定账号${account}，不进行绑定操作！`)
			return
		}

		let systemInfo = uni.getSystemInfoSync()
		let deviceId = generateUUID()
		let body = {}
		body.key = KEY_CLIENT_BIND
		body.timestamp = Date.now()
		body.data = {}
		body.data.uid = account
		body.data.channel = systemInfo.uniPlatform
		body.data.appVersion = APP_VERSION
		body.data.osVersion = systemInfo.osVersion
		body.data.packageName = APP_PACKAGE
		body.data.deviceId = deviceId
		body.data.deviceName = systemInfo.deviceModel
		body.data.language = systemInfo.appLanguage

		let response = await this.sendRequest(body)
		if (response && response.success) {
			// consoleLog(`WebSocket 账号绑定成功  账号：${account}`)
			this.isBind = true
		} else {
			consoleError(`WebSocket 账号绑定失败`, response && response.errMsg)
			this.isBind = false
		}
	}

	async bindRoom(message) {
		let body = {};
		body.key = KEY_CLIENT_ROOM;
		body.timestamp = Date.now()
		body.data = JSON.parse(JSON.stringify(message))

		let response = await this.sendRequest(body)
		if (response && response.success) {
			if (message.type === '1') {
				// consoleLog(`WebSocket 绑定房间成功`, body)

				// 重置未读数量
				this.roomId = message.roomId
				if (this.roomMap.hasOwnProperty(this.roomId)) {
					this.deleteRoom(this.roomMap[this.roomId], 'reset')
				}
			} else {
				// consoleLog(`WebSocket 解绑房间成功`, body)
				this.roomId = null
			}
		} else {
			consoleError(`WebSocket 绑定房间失败`, response && response.errMsg)
		}
	}

	async sendMessage(message) {
		let body = {};
		body.key = KEY_SEND_MESSAGE;
		body.timestamp = Date.now()
		body.data = JSON.parse(JSON.stringify(message))

		let response = await this.sendRequest(body)
		if (response && response.success) {
			// consoleLog(`WebSocket 消息发送成功，但服务器未响应`, body)
			this.onChatMessageChange({
				code: MESSAGE_SEND_OK,
				data: message,
			})
		} else {
			consoleError(`WebSocket 消息发送失败`, response && response.errMsg)
			this.onChatMessageChange({
				code: MESSAGE_SEND_ERROR,
				data: message,
			})
		}
	}

	// 撤回消息
	async revokeMessage(message) {
		let body = {};
		body.key = KEY_REVOKE_MESSAGE;
		body.timestamp = Date.now()
		body.data = JSON.parse(JSON.stringify(message))

		let response = await this.sendRequest(body)
		if (response && response.success) {
			// consoleLog(`WebSocket 消息撤回操作提交成功，但服务器未响应`, body)
			this.onChatMessageChange({
				code: MESSAGE_REVOKE_OK,
				data: message,
			})
		} else {
			consoleError(`WebSocket 消息撤回操作提交失败`, response && response.errMsg)
			this.onChatMessageChange({
				code: MESSAGE_REVOKE_ERROR,
				data: message,
			})
		}
	}

	sendRequest(body) {
		let data = {}
		data.type = SENT_BODY
		data.content = JSON.stringify(body)
		return new Promise((resolve, reject) => {
			this.socketInstance.send({
				data: JSON.stringify(data),
				success() {
					resolve({
						success: true
					})
				},
				fail(e) {
					resolve({
						success: false,
						errMsg: e
					})
				}
			})
		})
	}

	pong() {
		let data = {};
		data.type = PONG;
		data.content = PONG_BODY;
		this.socketInstance.send({
			data: JSON.stringify(data)
		});
	}

	// 做监听用，勿删
	onRoomMapChange(roomMap) {}
	onChatMessageChange(replay) {}
	onUnreadCountChange(unreadCount) {}
}

function generateUUID() {
	let d = Date.now()
	let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
		let r = (d + Math.random() * 16) % 16 | 0
		d = Math.floor(d / 16)
		return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
	})
	return uuid.replace(/-/g, '')
}

function consoleLog(...optionalParams) {
	console.log(...optionalParams)
}

function consoleError(...optionalParams) {
	console.error(...optionalParams)
}

export default WebSocket